package org.dei.perla.sys.environments;

import java.util.ArrayList;

import org.dei.perla.component.*;
import org.dei.perla.parser.clauses.*;
import org.dei.perla.parser.expressions.*;
import org.dei.perla.parser.statements.*;
import org.dei.perla.parser.util.Duration;
import org.dei.perla.sys.device.fpc.FunctionalityProxyComponent;
import org.dei.perla.sys.query.llqruntime.datacollection.*;
import org.dei.perla.sys.query.llqruntime.datastructures.*;
import org.dei.perla.sys.query.llqruntime.execution.LLExecutionInfo;
import org.dei.perla.sys.query.sqruntime.SetInformation;
import org.dei.perla.sys.registry.Registry;
import org.dei.perla.utils.logger.LogRecord.*;

/**
 * Questa classe si occupa della creazione di tutte le strutture dati necessarie all'esecuzione di una
 * query in PerLa. 
 * @author Camplani, Fortunato, Marelli, Rota et al.
 */
public class QueryAnalyzer extends Component {

	/**
	 * Costruttore private, per essere singleton
	 * @param parName Il nome del componente (QueryAnalyzer) da passare a Component
	 */
	private QueryAnalyzer(String parName) {
		super(parName);
	}
	
	/**
	 * Reference all'unica istanza
	 * @see costruttore
	 */
	private static QueryAnalyzer pInstance = null;
	
	/**
	 * Usato per notificare al sistema cio' che viene compiuto dal QueryAnalyzer
	 * @param parMessageType Il tipo del messaggio 
	 * @param parMessage  Il messaggio
	 * @param parVerbosity La verbosità minima del messaggio per essere stampato a video
	 * @see Logger
	 */
	private static void notifyToLogger(Type parMessageType, String parMessage, Verbosity parVerbosity) {
			getInstance().loggerNotify(parMessageType, parMessage, parVerbosity);
	}
	
	/**
	 * @return L'unica instanza del QueryAnalyzer
	 */
	public static QueryAnalyzer getInstance() {
		if (pInstance == null) {
			pInstance = new QueryAnalyzer("QueryAnalyzer");
			return pInstance;
		}
		else return pInstance;	
	}
	
	/**
	 *Registra la query di PerLa nel sistema. Per ogni Query da eseguire e' sufficente
	 *chiamare questo metodo che crea tutto il necessario e lo mette in opera.
	 */
	public static void registerQuery(Query parQuery) {
		notifyToLogger(Type.Information,"Aggiunta query in corso",Verbosity.High);
		createLLStatements(parQuery.getLLStatements());	
		createHLStatements(parQuery.getHLStatements());
		createSetParametersEnvironments(parQuery.getSetStatements());
	}
	
	/**
	 * Metodo che si occupa di creare gli <code>Statement</code> di basso livello
	 * @param parList Una lista di statement
	 */
	private static void createLLStatements(LLStatementList parList) {
		
		//...qualche dichiarazione
		SamplingData tTable;
		Node tWhere;
		LLEnvironment tEnvironment;
		LLDataCollectionInfo tDCInfo;
		LLExecutionInfo tEXInfo;  
		Buffer tBuffer;
			
		//..per ogni statement di basso livello
		for (LLStatement tStatement : parList) {	
			
			//...tutte le cose condivise dai vari LLDataCollectionInfo
			tEnvironment = new LLEnvironment();
			tTable = getFrequenciesTable(tStatement);
			tWhere = getWhereCondition(tStatement);
			
			for (FunctionalityProxyComponent tFPC : Registry.askRegistryForFPCs(tStatement)) {
				tFPC.getClass();
				//...per ogni FPC ottenuto dal registry serve un DC, un LLQExec e un LB
				tBuffer = new Buffer();
				tDCInfo = new LLDataCollectionInfo(tStatement,tFPC,tTable,tWhere,tBuffer);
				tEXInfo = new LLExecutionInfo(tStatement,tBuffer);
				tEXInfo.hashCode(); //Rimuovere, serve per togliere warning
				
				//..creo lo stack di basso livello
				tEnvironment.addStackToEnvironment(new LLStack(new DataCollector(tDCInfo),new Object()));
			}
			
			//...e aggiungo il tutto al contesto
		ActiveSession.addToSession(tEnvironment);
		}
		notifyToLogger(Type.Information,"Terminata creazione dei LL Environments",Verbosity.Low);
	}
	
	/**
	 * Costruisce la tabella con le clausole IF-EVERY-ELSE
	 */
	private static SamplingData getFrequenciesTable(LLStatement parStatement) {
		
		SamplingClause tClause = parStatement.getSamplingClause();
				
		if (tClause instanceof SamplingTimeBased) {
			notifyToLogger(Type.Information, "Sampling is TIME based", Verbosity.Low);
			ArrayList<FrequencyRecord> tArrayList = new ArrayList<FrequencyRecord>();
				for (SamplingIfEvery tSampling : ((SamplingTimeBased) tClause).getPeriods()) {
					Duration tDuration = null;					
			
					try {
						tDuration = new Duration(tSampling.getSamplingRate().getResult().getValueFloat(),tSampling.getTimeUnit());
					    } 
					catch (Exception e) {
						notifyToLogger(Type.Error, "Errore nel recupero del valore della frequenza", Verbosity.Low);
					    }
				
			tArrayList.add(new FrequencyRecord(tSampling.getCondition(),tDuration));	
								
			}
		
			
		return new SamplingData(tArrayList,((SamplingTimeBased) tClause).getOperation(),((SamplingTimeBased) tClause).getRefreshClause());	
		
		}else { 
			notifyToLogger(Type.Information, "Sampling is EVENT based, no sampling data needed", Verbosity.Low);
			return null;
			}
	}
	
	/**
	 * Metodo interno per il recupero della clausola WHERE
	 */
	private static Node getWhereCondition(LLStatement parStatement) {
		return parStatement.getSamplingClause().getWhereClause().getCondition();
	}
	
	
	/**
	 * Metodo che si occupa di creare gli <code>Statement</code> di alto livello
	 * @param parList Una lista di statement
	 */
	private static void createHLStatements(HLStatementList parList) {
		
	}

	/**
	 * Metodo che si occupa di creare gli <code>Statement</code> per le Actuation Queries
	 * @param parList la lista di SetParameters Query
	 */
	private static void createSetParametersEnvironments(SetParametersStatementList parList) {
		for (SetParametersStatement tStatement : parList) {
				ActiveSession.addToSession(new SetEnvironment(new SetInformation(tStatement,tStatement.getAttributesToSet(),tStatement.getIDs())));
		}
	}

}
